﻿namespace Webinator.Util
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// A dynamic json object structure, built using IDictionary.
    /// </summary>
    public class DynamicJsonObject : DynamicObject
    {
        /// <summary>
        /// The dictionary.
        /// </summary>
        private readonly IDictionary<string, object> _dictionary;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicJsonObject"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// Thrown if the dictionary is null.
        /// </exception>
        public DynamicJsonObject(IDictionary<string, object> dictionary)
        {
            if (dictionary == null)
            {
                throw new ArgumentNullException("dictionary");
            }

            _dictionary = dictionary;
        }

        /// <summary>
        /// Outputs the json to a string.
        /// </summary>
        /// <returns>
        /// A string containing the json.
        /// </returns>
        public override string ToString()
        {
            var sb = new StringBuilder("{");
            ToString(sb);
            return sb.ToString();
        }

        /// <summary>
        /// Attempts to retrieve the member.
        /// </summary>
        /// <param name="binder">The binder.</param>
        /// <param name="result">The result.</param>
        /// <returns>
        /// A boolean indicating the success of the operation.
        /// </returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (!_dictionary.TryGetValue(binder.Name, out result))
            {
                // return null to avoid exception.  caller can check for null this way...
                result = null;
                return true;
            }

            var dictionary = result as IDictionary<string, object>;
            if (dictionary != null)
            {
                result = new DynamicJsonObject(dictionary);
                return true;
            }

            var arrayList = result as ArrayList;
            if (arrayList != null && arrayList.Count > 0)
            {
                if (arrayList[0] is IDictionary<string, object>)
                {
                    result = new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)));
                }
                else
                {
                    result = new List<object>(arrayList.Cast<object>());
                }
            }

            return true;
        }

        /// <summary>
        /// Outputs the json in the StringBuilder to a string.
        /// </summary>
        /// <param name="sb">
        /// The StringBuilder.
        /// </param>
        private void ToString(StringBuilder sb)
        {
            var firstInDictionary = true;
            foreach (var pair in _dictionary)
            {
                if (!firstInDictionary)
                {
                    sb.Append(",");
                }

                firstInDictionary = false;
                var value = pair.Value;
                var name = pair.Key;
                if (value is string)
                {
                    sb.AppendFormat("{0}:\"{1}\"", name, value);
                }
                else if (value is IDictionary<string, object>)
                {
                    new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
                }
                else if (value is ArrayList)
                {
                    sb.Append(name + ":[");
                    var firstInArray = true;
                    foreach (var arrayValue in (ArrayList)value)
                    {
                        if (!firstInArray)
                        {
                            sb.Append(",");
                        }

                        firstInArray = false;
                        if (arrayValue is IDictionary<string, object>)
                        {
                            new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
                        }
                        else if (arrayValue is string)
                        {
                            sb.AppendFormat("\"{0}\"", arrayValue);
                        }
                        else
                        {
                            sb.AppendFormat("{0}", arrayValue);
                        }
                    }

                    sb.Append("]");
                }
                else
                {
                    sb.AppendFormat("{0}:{1}", name, value);
                }
            }

            sb.Append("}");
        }
    }
}
